//
// Created by alexa on 5/13/2024.
//

#include <conio.h>
#include <type_traits>
#include "UnitsMenu.h"
#include "../../valid-input/ValidInput.h"
#include "../../../exceptions/EntityNotFound.h"
#include "../../keydown-handler/KeydownHandler.h"

UnitsMenu::UnitsMenu(i18n::Translator *t, UserInterface *ui, UnitsService *service) :
        Menu(t, ui),
        service(service) {}

void UnitsMenu::displayMenu() {
    printMenu();
    return keydownHandle(ui->getPressedKey, {
            {Keys::TAB,   [this]() {
                ui->toggleLocale();
                return displayMenu();
            }},
            {Keys::ESC,   [this]() {
                ui->setCurrentPage(Pages::MAIN_MENU);
                return;
            }},
            {Keys::KEY_1, [this]() {
                return displayUnitsList();
            }},
            {Keys::KEY_2, [this]() {
                CombatUnit *unit = findUnit();
                return displayUnitInfo(unit);
            }},
            {Keys::KEY_3, [this]() {
                return createUnit();
            }},
            {Keys::KEY_4, [this]() {
                CombatUnit *unit = findUnit();
                return deleteUnit(unit);
            }}
    });
}

void UnitsMenu::printMenu() {
    clearScreen();
    printKeybinds();
    cout << (*t)("title", "units.main") << endl;
    cout << "1. " << (*t)("option1", "units.main") << endl;
    cout << "2. " << (*t)("option2", "units.main") << endl;
    cout << "3. " << (*t)("option3", "units.main") << endl;
    cout << "4. " << (*t)("option4", "units.main") << endl;
    cout << (*t)("select-action", "general",
                 {
                         {"min", "1"},
                         {"max", "4"}
                 });
}

void UnitsMenu::deleteUnit(CombatUnit *unit) {
    if (!unit) return;
    service->deleteOne(unit->getId());
    cout << (*t)("successfully-deleted", "units.delete", {{"unit-id", unit->getId()}}) << endl;
    cout << (*t)("continue", "general");
    _getch();
    ui->setCurrentPage(Pages::UNITS_MENU);
}

CombatUnit *UnitsMenu::findUnit() {
    cout << endl;
    string unitId = vi::inputString((*t)("enter-unit-id", "units.general"));
    try {
        return service->getById(unitId);
    } catch (EntityNotFound e) {
        cout << (*t)("unit-doesnt-exist", "units.general") << endl;
        cout << (*t)("continue", "general");
        _getch();
        ui->setCurrentPage(Pages::UNITS_MENU);
        return nullptr;
    }
}

void UnitsMenu::displayUnitInfo(CombatUnit *unit) {
    if (!unit) return;
    SelfPropelledLauncher *selfPropelledLauncher = dynamic_cast<SelfPropelledLauncher *>(unit);
    Launcher *launcher = dynamic_cast<Launcher *>(unit);
    if (selfPropelledLauncher) {
        printSelfPropelledLauncherInfo(selfPropelledLauncher);
    } else if (launcher) {
        printLauncherInfo(launcher);
    }

    cout << endl << (*t)("add-crew", "units.info") << endl;
    cout << (*t)("remove-crew", "units.info") << endl;
    cout << (*t)("add-missile", "units.info") << endl;
    cout << (*t)("remove-missile", "units.info") << endl;
    cout << (*t)("change-required-specs", "units.info") << endl;
    cout << (*t)("move-unit", "units.info") << endl;
    if (service->isPropelledType(unit)) {
        cout << (*t)("refuel-unit", "units.info") << endl;
    }
    cout << (*t)("set-aim", "units.info") << endl;
    cout << (*t)("shoot", "units.info") << endl;
    cout << (*t)("reload", "units.info") << endl;
    cout << (*t)("delete", "units.info") << endl;
    return keydownHandle(ui->getPressedKey, {
            {Keys::TAB,   [this, unit]() {
                ui->toggleLocale();
                return displayUnitInfo(unit);
            }},
            {Keys::ESC,   [this]() {
                ui->setCurrentPage(Pages::UNITS_MENU);
                return;
            }},
            {Keys::KEY_A, [this, unit]() {
                return setCrewToUnit(unit);
            }},
            {Keys::KEY_M, [this, unit]() {
                return removeCrewFromUnit(unit);
            }},
            {Keys::KEY_J, [this, unit]() {
                return setMissileToUnit(unit);
            }},
            {Keys::KEY_K, [this, unit]() {
                return removeMissileFromUnit(unit);
            }},
            {Keys::KEY_S, [this, unit]() {
                unit->setRequiredSpecializations(selectRequiredSpecializationsForUnit());
                service->saveUnit(unit);
                return displayUnitInfo(unit);
            }},
            {Keys::KEY_O, [this, unit]() {
                return moveUnit(unit);
            }},
            {Keys::KEY_F, [this, unit]() {
                service->refuelUnit(unit);
                return displayUnitInfo(unit);
            }},
            {Keys::KEY_B, [this, unit]() {
                return setAimForUnit(unit);
            }},
            {Keys::KEY_T, [this, unit]() {
                return unitShoot(unit);
            }},
            {Keys::KEY_R, [this, unit]() {
                return unitReload(unit);
            }},
            {Keys::KEY_D, [this, unit]() {
                return deleteUnit(unit);
            }}
    });
}

void UnitsMenu::printLauncherInfo(Launcher *unit) {
    clearScreen();
    printKeybinds();
    cout << endl;
    cout << (*t)("id", "units.info") << ": " << unit->getId() << endl;
    cout << (*t)("name", "units.info") << ": " << unit->getName() << endl;
    if (service->isPropelledType(unit)) {
        cout << (*t)("type", "units.info") << ": " << (*t)("self-propelled-launcher-type", "units.info") << endl;
    } else {
        cout << (*t)("type", "units.info") << ": " << (*t)("launcher-type", "units.info") << endl;
    }
    cout << (*t)("rails-count", "units.info") << ": " << unit->getRailsCount() << endl;
    cout << (*t)("shots-delay", "units.info") << ": "
         << unit->getShotsDelay() << " " << (*t)("s", "measurement-units")
         << endl;
    cout << (*t)("missiles-count", "units.info") << ": " << unit->getMissilesCount() << endl;
    cout << (*t)("missile-id", "units.info") << ": ";
    if (!unit->getMissileId().empty()) {
        cout << unit->getMissileId() << endl;
    } else {
        cout << (*t)("missile-type-not-specified", "units.info") << endl;
    }

    cout << (*t)("required-specializations", "units.info") << ": " << endl;
    vector<Specialization> requiredSpecs = unit->getRequiredSpecializations();
    if (requiredSpecs.empty()) {
        cout << (*t)("unit-doesnt-require-crew", "units.info") << endl;
    } else {
        for (auto const &spec: requiredSpecs) {
            cout << (*t)(specToString(spec), "soldiers.specializations") << endl;
        }
    }

    cout << (*t)("crew-id", "units.info") << ": ";
    if (unit->getCrewId() == "") {
        cout << (*t)("crew-is-not-set", "units.info") << endl;
    } else {
        cout << unit->getCrewId() << endl;
    }

    vector<Specialization> missingSpecializations = service->getMissingSpecializations(unit->getId());

    if (!missingSpecializations.empty()) {
        cout << (*t)("missing-specializations", "units.info") << endl;
        for (const auto &spec: missingSpecializations) {
            cout << (*t)(specToString(spec), "soldiers.specializations") << endl;
        }
    }
    cout << (*t)("current-coordinates", "units.info", {{"posX", to_string(unit->getPosX())},
                                                       {"posY", to_string(unit->getPosY())}}) << endl;

    cout << (*t)("current-aim", "units.info", {{"aimX", to_string(unit->getAimX())},
                                               {"aimY", to_string(unit->getAimY())}}) << endl;
}

void UnitsMenu::printSelfPropelledLauncherInfo(SelfPropelledLauncher *unit) {
    printLauncherInfo(unit);
    cout << (*t)("length", "units.info") << ": "
         << unit->getLength() << " " << (*t)("m", "measurement-units")
         << endl;
    cout << (*t)("width", "units.info") << ": "
         << unit->getWidth() << " " << (*t)("m", "measurement-units")
         << endl;
    cout << (*t)("weight", "units.info") << ": "
         << unit->getWeight() << " " << (*t)("kg", "measurement-units")
         << endl;
    cout << (*t)("max-speed", "units.info") << ": "
         << unit->getMaxSpeed() << " " << (*t)("km/h", "measurement-units")
         << endl;
    cout << (*t)("power-reserve", "units.info") << ": "
         << unit->getPowerReserve() << " " << (*t)("km", "measurement-units")
         << endl;
    cout << (*t)("remaining-power-reserve", "units.info") << ": "
         << unit->getRemainingPowerReserve() << " " << (*t)("km", "measurement-units")
         << endl;
    cout << (*t)("preparing-time", "units.info") << ": "
         << unit->getPreparingTime() << " " << (*t)("s", "measurement-units")
         << endl;
}

void UnitsMenu::createUnit() {
    clearScreen();
    string name = vi::inputString((*t)("enter-name", "units.create"));
    int launcherType = vi::inputList(
            {
                    (*t)("launcher-type", "units.info"),
                    (*t)("self-propelled-launcher-type", "units.info")
            }, (*t)("choose-type", "units.create"));
    cout << endl;
    int railsCount = -1;
    while (railsCount <= 0) {
        railsCount = vi::inputInt((*t)("enter-rails-count", "units.create"));
    }
    float shotsDelay = -1;
    while (shotsDelay <= 0) {
        shotsDelay = vi::inputFloat(
                (*t)("enter-shots-delay", "units.create", {{"unit", (*t)("s", "measurement-units")}}));
    }
    if (launcherType == 1) {
        Launcher *launcher = service->createLauncher(name, railsCount, shotsDelay);
        return displayUnitInfo(launcher);
    }
    float length = -1;
    while (length <= 0) {
        length = vi::inputFloat((*t)("enter-length", "units.create", {{"unit", (*t)("m", "measurement-units")}}));
    }
    float width = -1;
    while (width <= 0) {
        width = vi::inputFloat((*t)("enter-width", "units.create", {{"unit", (*t)("m", "measurement-units")}}));
    }
    float weight = -1;
    while (weight <= 0) {
        weight = vi::inputFloat((*t)("enter-weight", "units.create", {{"unit", (*t)("kg", "measurement-units")}}));
    }
    float maxSpeed = -1;
    while (maxSpeed <= 0) {
        maxSpeed = vi::inputFloat(
                (*t)("enter-max-speed", "units.create", {{"unit", (*t)("km/h", "measurement-units")}}));
    }
    float powerReserve = -1;
    while (powerReserve <= 0) {
        powerReserve = vi::inputFloat(
                (*t)("enter-power-reserve", "units.create", {{"unit", (*t)("km", "measurement-units")}}));
    }
    float preparingTime = -1;
    while (preparingTime <= 0) {
        preparingTime = vi::inputFloat(
                (*t)("enter-preparing-time", "units.create", {{"unit", (*t)("s", "measurement-units")}}));
    }
    SelfPropelledLauncher *launcher = service->createSelfPropelledLauncher(name, railsCount, shotsDelay, length, width,
                                                                           maxSpeed, weight, powerReserve,
                                                                           preparingTime);
    return displayUnitInfo(launcher);
}

void UnitsMenu::displayUnitsList() {
    vector<CombatUnit *> units = service->getAll();
    printUnitsList(units);
    cout << endl << (*t)("get-info", "units.list");
    return keydownHandle(ui->getPressedKey, {
            {Keys::TAB,   [this]() {
                ui->toggleLocale();
                return displayUnitsList();
            }},
            {Keys::ESC,   [this]() {
                ui->setCurrentPage(Pages::UNITS_MENU);
                return;
            }},
            {Keys::KEY_I, [this]() {
                return findAndDisplayUnitInfo();
            }}
    });
}

void UnitsMenu::printUnitsList(vector<CombatUnit *> list) {
    clearScreen();
    printKeybinds();
    cout << (*t)("title", "units.list") << endl;
    if (list.empty()) {
        cout << (*t)("empty-list", "general") << endl;
        return;
    }
    for (const auto &unit: list) {
        cout << endl;
        cout << (*t)("id", "units.info") << ": " << unit->getId() << endl;
        cout << (*t)("name", "units.info") << ": " << unit->getName() << endl;
        if (service->isPropelledType(unit)) {
            cout << (*t)("type", "units.info") << ": " << (*t)("self-propelled-launcher-type", "units.info") << endl;
        } else {
            cout << (*t)("type", "units.info") << ": " << (*t)("launcher-type", "units.info") << endl;
        }
        vector<Specialization> missingSpecs = service->getMissingSpecializations(unit->getId());
        if (!missingSpecs.empty()) {
            if (unit->getCrewId().empty()) {
                cout << (*t)("crew-is-not-set", "units.info") << endl;
            }
            cout << (*t)("missing-members-number", "units.list") << ": " << missingSpecs.size() << endl;
        }
        if (unit->getMissileId().empty()) {
            cout << (*t)("missile-type-not-specified", "units.info") << endl;
        }
    }
}

void UnitsMenu::setCrewToUnit(CombatUnit *unit) {
    string crewId = vi::inputString((*t)("enter-crew-id", "crews.general"));
    try {
        service->setCrew(unit->getId(), crewId);
        cout << (*t)("crew-added", "units.info", {{"crew-id", crewId}}) << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    } catch (EntityNotFound &e) {
        cout << (*t)("crew-doesnt-exist", "crews.general") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
}

void UnitsMenu::removeCrewFromUnit(CombatUnit *unit) {
    string prevCrewId = unit->getCrewId();
    service->setCrew(unit->getId(), "");
    if (!prevCrewId.empty()) {
        cout << (*t)("crew-removed", "units.info", {{"crew-id", prevCrewId}}) << endl;
        cout << (*t)("continue", "general");
        _getch();
    }
    return displayUnitInfo(unit);
}

void UnitsMenu::findAndDisplayUnitInfo() {
    CombatUnit *unit = findUnit();
    return displayUnitInfo(unit);
}

vector<Specialization> UnitsMenu::selectRequiredSpecializationsForUnit() {
    vector<Specialization> selectedSpecs;
    while (1) {
        clearScreen();
        cout << (*t)("current-selected-required-specs", "units.create") << endl;
        if (selectedSpecs.empty()) {
            cout << (*t)("no-selected-required-specs", "units.create") << endl;
        } else {
            for (auto const &spec: selectedSpecs) {
                cout << (*t)(specToString(spec), "soldiers.specializations") << endl;
            }
        }
        int chosenSpecialization = vi::inputList(
                {
                        (*t)("Commander", "soldiers.specializations"),
                        (*t)("Gunner", "soldiers.specializations"),
                        (*t)("MechanicDriver", "soldiers.specializations"),
                        (*t)("Charger", "soldiers.specializations"),
                        (*t)("end-selection", "units.create")
                }, (*t)("select-required-specs", "units.create"));
        if (chosenSpecialization == 5) {
            break;
        }
        selectedSpecs.push_back(static_cast<Specialization>(chosenSpecialization - 1));
    }
    return selectedSpecs;
}

void UnitsMenu::setMissileToUnit(CombatUnit *unit) {
    string missileId = vi::inputString((*t)("enter-missile-id", "missiles.general"));
    try {
        service->setMissile(unit->getId(), missileId);
        cout << (*t)("missile-added", "units.info", {{"missile-id", missileId}}) << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    } catch (EntityNotFound &e) {
        cout << (*t)("missile-doesnt-exist", "missiles.general") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
}

void UnitsMenu::removeMissileFromUnit(CombatUnit *unit) {
    string prevMissileId = unit->getMissileId();
    service->setMissile(unit->getId(), "");
    if (!prevMissileId.empty()) {
        cout << (*t)("missile-removed", "units.info", {{"missile-id", prevMissileId}}) << endl;
        cout << (*t)("continue", "general");
        _getch();
    }
    return displayUnitInfo(unit);
}

void UnitsMenu::moveUnit(CombatUnit *unit) {
    if (!service->getMissingSpecializations(unit->getId()).empty()) {
        cout << (*t)("cant-complete", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    float newX = vi::inputFloat((*t)("enter-coordinates", "units.info"));
    float newY = vi::inputFloat("");
    SelfPropelledLauncher *selfPropelledLauncher = nullptr;
    if (service->isPropelledType(unit)) {
        selfPropelledLauncher = dynamic_cast<SelfPropelledLauncher *>(unit);
    }
    if (selfPropelledLauncher != nullptr && selfPropelledLauncher->getRemainingPowerReserve() <= 0) {
        cout << (*t)("run-out-of-reserve", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    float distance = service->moveCombatUnit(unit->getId(), newX, newY);
    float time = distance / 7;
    if (selfPropelledLauncher != nullptr) {
        time = distance / selfPropelledLauncher->getMaxSpeed();
    }
    cout << (*t)("time-to-move", "units.info", {{"time",     to_string(time)},
                                                {"distance", to_string(distance)}}) << endl;
    cout << (*t)("continue", "general");
    _getch();
    return displayUnitInfo(unit);
}

void UnitsMenu::setAimForUnit(CombatUnit *unit) {
    if (!service->getMissingSpecializations(unit->getId()).empty()) {
        cout << (*t)("cant-complete", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    float aimX = vi::inputFloat((*t)("enter-aim-coordinates", "units.info"));
    float aimY = vi::inputFloat("");
    service->setAimForCombatUnit(unit->getId(), aimX, aimY);
    return displayUnitInfo(unit);
}

//TODO добавить вывод времени полёта снаряда

void UnitsMenu::unitShoot(CombatUnit *unit) {
    if (!service->getMissingSpecializations(unit->getId()).empty()) {
        cout << (*t)("cant-complete", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    if (unit->getMissileId().empty()) {
        cout << (*t)("missile-isnt-set", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    Launcher *launcher = dynamic_cast<Launcher *>(unit);
    if (launcher->getMissilesCount() == 0) {
        cout << (*t)("no-missiles", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }

    unordered_map<string, float> res = service->unitShoot(unit);
    cout << (*t)("shot-fired", "units.info",
                 {
                         {"aimX",     to_string(res["aimX"])},
                         {"aimY",     to_string(res["aimY"])},
                         {"distance", to_string(res["distance"])}
                 }) << endl;
    cout << (*t)("continue", "general");
    _getch();
    return displayUnitInfo(unit);
}

void UnitsMenu::unitReload(CombatUnit *unit) {
    if (!service->getMissingSpecializations(unit->getId()).empty()) {
        cout << (*t)("cant-complete", "units.info") << endl;
        cout << (*t)("continue", "general");
        _getch();
        return displayUnitInfo(unit);
    }
    service->reloadUnit(unit->getId());
    return displayUnitInfo(unit);
}
